package com.net.xpay.web.controller.open;

import com.alipay.api.AlipayApiException;
import com.alipay.api.response.AlipayFundAccountbookCreateResponse;
import com.alipay.api.response.AlipayFundAccountbookNotifySubscribeResponse;
import com.google.common.base.Strings;
import com.net.common.concurrent.ThreadUtil;
import com.net.common.util.LocalDateUtil;
import com.net.common.util.SerializationUtil;
import com.net.xpay.alipay.model.PayTransAuthInfoModel;
import com.net.xpay.alipay.safePay.PoseidonSafePayAlipayClient;
import com.net.xpay.common.domain.AlipayUserSignAuthRecord;
import com.net.xpay.common.domain.DataSnap;
import com.net.xpay.common.domain.UserShard;
import com.net.xpay.common.domain.open.OpenAlipayAuthToken;
import com.net.xpay.common.enums.DataSnapType;
import com.net.xpay.common.enums.open.AuthStatus;
import com.net.xpay.common.enums.user.AlipayUserSignAuthRecordStatus;
import com.net.xpay.common.manager.AlipayUserSignAuthRecordManager;
import com.net.xpay.common.manager.DataSnapManager;
import com.net.xpay.common.manager.UserProfileManager;
import com.net.xpay.common.manager.UserShardManager;
import com.net.xpay.common.manager.open.OpenAlipayAuthTokenManager;
import com.net.xpay.core.domain.AlipayUser;
import com.net.xpay.core.domain.safePay.SafePayBookAccount;
import com.net.xpay.core.enums.AlipayUserStatus;
import com.net.xpay.core.enums.SafePayBookAccountBookStatus;
import com.net.xpay.core.enums.SafePayBookAccountSignStatus;
import com.net.xpay.core.helper.EnvironmentHelper;
import com.net.xpay.core.manager.AlipayUserManager;
import com.net.xpay.core.manager.safePay.SafePayBookAccountManager;
import com.net.xpay.core.model.AlipayUserSearchModel;
import com.net.xpay.core.utils.GsonUtil;
import com.net.shard.ShardDataSource;
import com.net.shard.ShardDataSourceConfigurer;
import com.net.shard.ShardUtil;
import com.net.shard.domain.ShardConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

@Controller
@Slf4j
@RequestMapping("/open")
@Api(tags = "第三方开放调用/支付宝签约通知", description = "第三方开放调用 支付宝签约通知 net")
public class AlipayNotifyController {

    @Autowired
    private AlipayUserManager alipayUserManager;

    @Autowired
    private UserShardManager userShardManager;

    @Autowired
    private OpenAlipayAuthTokenManager openAlipayAuthTokenManager;

    @Autowired
    private SafePayBookAccountManager safePayBookAccountManager;

    @Autowired
    @Qualifier("shardDataSource")
    private DataSource shardDataSource;

    @Autowired
    private PoseidonSafePayAlipayClient poseidonSafePayAlipayClient;

    @Autowired
    private EnvironmentHelper environmentHelper;

    @Autowired
    private DataSnapManager dataSnapManager;

    @Autowired
    private AlipayUserSignAuthRecordManager alipayUserSignAuthRecordManager;

    @Autowired
    private UserProfileManager userProfileManager;

    /**
     * 支付宝商户签约通知
     * @param request
     * @throws IOException
     */
    @ApiOperation(value = "支付宝商户签约通知")
    @PostMapping("/notify")
    @ResponseBody
    public String gateway(HttpServletRequest request) throws IOException {
        log.info("gateway params={}", SerializationUtil.obj2String(request.getParameterMap()));
        String bizContent = request.getParameter("biz_content");
        log.info("gateway biz_content={}", bizContent);
        PayTransAuthInfoModel authInfo = GsonUtil.getGson().fromJson(bizContent, PayTransAuthInfoModel.class);
        String isvBizId = authInfo.getNotifyContext().getTriggerContext().getOutBizNo();
        PayTransAuthInfoModel.Detail detail = authInfo.getDetail();


        if (isvBizId.contains("@")) {
            //外部应用授权的
            return processOpenAppToken(detail, isvBizId, bizContent);
        }

        //判断一下是不是新签约渠道的
        AlipayUserSignAuthRecord record = alipayUserSignAuthRecordManager.getByBizNo(isvBizId);
        if (record != null) {
            //签约时间
            LocalDateTime authTime = LocalDateUtil.getDateTimeOfTimestamp(detail.getAuthTime());
            isvBizId = record.getIsvBizId();//转换成内部的 和之前统一的方式
            record.setAuthToken(detail.getAppAuthToken());
            record.setRefreshToken(detail.getAppRefreshToken());
            record.setExpiresIn(authTime.plusSeconds(Long.parseLong(detail.getExpiresIn())));
            record.setReExpiresIn(authTime.plusSeconds(Long.parseLong(detail.getReExpiresIn())));
            record.setAlipayUserId(detail.getUserId());
            record.setAuthEndTime(LocalDateTime.now());
            record.setSignEndTime(LocalDateTime.now());
            record.setAuthStatus(AuthStatus.YES);
            record.setStatus(AlipayUserSignAuthRecordStatus.FINISH);
            alipayUserSignAuthRecordManager.update(record);
        }
        //内部应用的授权
        return processInnerToken(detail, isvBizId, bizContent);
    }

    /**
     * 支付宝安全发商户签约通知
     * @param request
     * @throws IOException
     */
    @ApiOperation(value = "支付宝安全发商户签约通知")
    @PostMapping("/notify2")
    @ResponseBody
    public String gateway2(HttpServletRequest request) throws IOException {
        log.info("gateway2 params={}", SerializationUtil.obj2String(request.getParameterMap()));

        String notifyType = request.getParameter("notify_type");
        if (Strings.isNullOrEmpty(notifyType)) {
            return "";
        }
        //目前只处理安全发签约的通知 其他通知用websocket拉取
        if (!"dut_user_sign".equals(notifyType)) {
            return "success";
        }
        String externalAgreementNo = request.getParameter("external_agreement_no");
        if (Strings.isNullOrEmpty(externalAgreementNo)) {
            return "";
        }

        try {
            Long userId = Long.parseLong(externalAgreementNo.split("-")[0]);
            UserShard userShard = userShardManager.getById(userId);
            ShardUtil.setShardContext(userShard.getShardId());
            return saveSafePayBookAccount(request, externalAgreementNo) ? "success" : "";
        } finally {
            ShardUtil.cleanShardContext();
        }
    }


    /**
     * 支付宝灵工卡签约通知
     */
    @ApiOperation(value = "支付宝灵工卡签约通知")
    @PostMapping("/notify3")
    @ResponseBody
    public String gateway3(HttpServletRequest request) throws IOException {
        log.info("gateway3 params={}", SerializationUtil.obj2String(request.getParameterMap()));

        return "success";
    }

    private boolean saveSafePayBookAccount(HttpServletRequest request, String externalAgreementNo) {
        boolean success = false;
        //失败重试10秒
        LocalDateTime deadline = LocalDateTime.now().plusSeconds(10L);

        SafePayBookAccount account = null;
        while (LocalDateTime.now().isBefore(deadline)) {
            try {
                account = safePayBookAccountManager.findByExternalAgreementNo(externalAgreementNo);
                if(account == null) {
                    log.info("saveSafePayBookAccount not newest notify params={}", SerializationUtil.obj2String(request.getParameterMap()));
                    return true;
                }
                //通知id重复
                if (request.getParameter("notify_id").equals(account.getNotifyId())) {
                    log.warn("saveSafePayBookAccount notify_id repeat, notifyId={}", account.getNotifyId());
                    return true;
                }

                if (!Strings.isNullOrEmpty(account.getAgreementNo()) || !Strings.isNullOrEmpty(account.getBookId())) {
                    //保存下历史快照
                    log.warn("saveSafePayBookAccount save snap account={}", SerializationUtil.obj2String(account));
                    DataSnap dataSnap = DataSnap.getInstance();
                    dataSnap.setData(SerializationUtil.obj2String(account));
                    dataSnap.setType(DataSnapType.SAFE_PAY_BOOK_ACCOUNT);
                    dataSnap.setRelatedId(account.getId());
                    dataSnapManager.save(dataSnap);
                }

                account.setAlipayUid(request.getParameter("alipay_user_id"));
                account.setFuzzyAlipayLogonId(request.getParameter("alipay_logon_id"));
                account.setAgreementNo(request.getParameter("agreement_no"));
                account.setSignTime(request.getParameter("sign_time"));
                account.setSignInvalidTime(request.getParameter("invalid_time"));
                account.setLoginToken(request.getParameter("login_token"));
                account.setNotifyId(request.getParameter("notify_id"));
                account.setSignStatus(SafePayBookAccountSignStatus.of(request.getParameter("status")));

                success = true;
                log.info("saveSafePayBookAccount success externalAgreementNo={}", externalAgreementNo);
                break;
            } catch (Exception e) {
                log.info("saveSafePayBookAccount error retry externalAgreementNo={}", externalAgreementNo, e);
            }
        }

        if (success && account != null) {
            String merchantUserId = environmentHelper.getEnv() + "-" + account.getId().toString();

            //开通下账本
            try {
                AlipayFundAccountbookCreateResponse resp = poseidonSafePayAlipayClient.alipayFundAccountbookCreate(merchantUserId, account.getAgreementNo());
                log.info("saveSafePayBookAccount create book resp={}", SerializationUtil.obj2String(resp));
                if (resp.isSuccess() && !Strings.isNullOrEmpty(resp.getAccountBookId())) {
                    account.setBookId(resp.getAccountBookId());
                    account.setExtCardInfo(SerializationUtil.obj2String(resp.getExtCardInfo()));
                    account.setBookStatus(SafePayBookAccountBookStatus.YES);
                    safePayBookAccountManager.updateByPrimaryKeyWithVersion(account);
                    log.info("saveSafePayBookAccount create book success|externalAgreementNo={}", externalAgreementNo);

                    //失败重试30秒
                    deadline = LocalDateTime.now().plusSeconds(30L);
                    long t = System.currentTimeMillis();
                    while (LocalDateTime.now().isBefore(deadline)) {
                        ThreadUtil.safeSleep(2000L, "saveSafePayBookAccount subscribe sleep");
                        //订阅同名银行卡入金消息
                        AlipayFundAccountbookNotifySubscribeResponse subscribeResponse = poseidonSafePayAlipayClient.alipayFundAccountbookNotifySubscribe(account.getBookId(), account.getAgreementNo());
                        log.info("saveSafePayBookAccount subscribe resp={}|externalAgreementNo={}", SerializationUtil.obj2String(subscribeResponse), externalAgreementNo);
                        if (subscribeResponse.isSuccess() && "VALID".equals(subscribeResponse.getSubscribeStatus())) {
                            log.info("saveSafePayBookAccount subscribe success|cost={}|externalAgreementNo={}", System.currentTimeMillis() - t, externalAgreementNo);
                            return true;
                        } else {
                            log.info("saveSafePayBookAccount subscribe failed|externalAgreementNo={}|account={}|resp={}", externalAgreementNo, SerializationUtil.obj2String(account), SerializationUtil.obj2String(subscribeResponse));
                        }
                    }
                    log.info("saveSafePayBookAccount subscribe final failed|externalAgreementNo={}|account={}", externalAgreementNo, SerializationUtil.obj2String(account));
                } else {
                    log.warn("saveSafePayBookAccount create book failed|resp={}", SerializationUtil.obj2String(resp));
                    return false;
                }
            } catch (AlipayApiException e) {
                log.warn("saveSafePayBookAccount create book failed|externalAgreementNo={}", externalAgreementNo, e);
                return false;
            }
        }
        return false;
    }

    private String processOpenAppToken(PayTransAuthInfoModel.Detail detail, String isvBizId, String bizContent) {
        String result = "";
        //失败重试10秒
        LocalDateTime deadline = LocalDateTime.now().plusSeconds(10L);
        //签约时间
        LocalDateTime authTime = LocalDateUtil.getDateTimeOfTimestamp(detail.getAuthTime());
        while (LocalDateTime.now().isBefore(deadline)) {
            try {
                OpenAlipayAuthToken authToken = openAlipayAuthTokenManager.findByIsvBizId(isvBizId);
                if(authToken == null) {
                    log.info("processOpenAppToken not newest notify bizContent={}", bizContent);
                    return "success";
                }

                authToken.setAuthToken(detail.getAppAuthToken());
                authToken.setRefreshToken(detail.getAppRefreshToken());
                authToken.setExpiresIn(authTime.plusSeconds(Long.parseLong(detail.getExpiresIn())));
                authToken.setReExpiresIn(authTime.plusSeconds(Long.parseLong(detail.getReExpiresIn())));
                authToken.setAlipayUserId(detail.getUserId());
                authToken.setStatus(AuthStatus.YES);
                openAlipayAuthTokenManager.update(authToken);
                result = "success";
                break;
            } catch (Exception e) {
                log.info("processOpenAppToken error retry bizContent={}", bizContent, e);
            }
        }

        log.info("processOpenAppToken success bizContent={}", bizContent);

        return result;
    }

    private String processInnerToken(PayTransAuthInfoModel.Detail detail, String isvBizId, String bizContent) {
        boolean success;

        AlipayUser alipayUser;
        try {
            Long userId = Long.parseLong(isvBizId.split("-")[0]);
            UserShard userShard = userShardManager.getById(userId);
            ShardUtil.setShardContext(userShard.getShardId());
            success = saveAlipayUser(detail, isvBizId, bizContent);

            alipayUser = alipayUserManager.findByIsvBizId(isvBizId);
        } finally {
            ShardUtil.cleanShardContext();
        }
        if (alipayUser != null) {
            //不同用户可能绑定了同一个支付宝号，将其他的号上的支付宝授权也跟新一下
            processSameOtherAlipayUser(alipayUser);
        }

        if (success) {
            //发送success 支付宝就不会重复通知了
            return "success";
        } else {
            return "";
        }
    }

    private void processSameOtherAlipayUser(AlipayUser alipayUser) {
        ShardDataSourceConfigurer shardDataSourceConfigurer = ((ShardDataSource) shardDataSource).getShardDataSourceConfigurer();
        Map<Long, ShardConfig> shardConfigMap = shardDataSourceConfigurer.getShardConfigMap();
        for (Long shardId : shardConfigMap.keySet()) {
            try {
                ShardUtil.setShardContext(shardId);
                doProcessSameOtherAlipayUser(alipayUser);
            } catch (Exception e) {
                log.warn("processSameOtherAlipayUser error", e);
            } finally {
                ShardUtil.cleanShardContext();
            }
        }
    }

    private void doProcessSameOtherAlipayUser(AlipayUser param) {
        if (Strings.isNullOrEmpty(param.getAlipayNickName()) && Strings.isNullOrEmpty(param.getAlipayUserId())) {
            return;
        }

        AlipayUserSearchModel searchModel = new AlipayUserSearchModel();
        searchModel.setAlipayNickName(param.getAlipayNickName());
        searchModel.setAlipayUserId(param.getAlipayUserId());
        List<AlipayUser> alipayUsers = alipayUserManager.list(searchModel);
        if(CollectionUtils.isEmpty(alipayUsers)) {
            return;
        }
        for (AlipayUser user : alipayUsers) {
            if (user.getId().equals(param.getId())) {
                continue;
            }
            //失败重试10秒
            LocalDateTime deadline = LocalDateTime.now().plusSeconds(10L);
            while (LocalDateTime.now().isBefore(deadline)) {
                try {
                    user = alipayUserManager.getById(user.getId());
                    if (!user.getAlipayUserId().equals(param.getAlipayUserId())) {
                        log.warn("doProcessSameOtherAlipayUser error!search error|param={}|select={}", SerializationUtil.obj2String(param), SerializationUtil.obj2String(user));
                        continue;
                    }
                    user.setAuthToken(param.getAuthToken());
                    user.setRefreshToken(param.getRefreshToken());
                    user.setExpiresIn(param.getExpiresIn());
                    user.setReExpiresIn(param.getReExpiresIn());
                    user.setAlipayUserId(param.getAlipayUserId());
                    user.setStatus(AlipayUserStatus.YES);
                    alipayUserManager.updateByPrimaryKeyWithVersion(user);
                    log.info("doProcessSameOtherAlipayUser success||param={}|select={}", SerializationUtil.obj2String(param), SerializationUtil.obj2String(user));
                    break;
                } catch (Exception e) {
                    log.info("doProcessSameOtherAlipayUser error retry|param={}", SerializationUtil.obj2String(param),  e);
                }
            }
        }
    }

    private boolean saveAlipayUser(PayTransAuthInfoModel.Detail detail, String isvBizId, String bizContent) {
        boolean success = false;
        //失败重试10秒
        LocalDateTime deadline = LocalDateTime.now().plusSeconds(10L);
        //签约时间
        LocalDateTime authTime = LocalDateUtil.getDateTimeOfTimestamp(detail.getAuthTime());
        while (LocalDateTime.now().isBefore(deadline)) {
            try {
                AlipayUser alipayUser = alipayUserManager.findByIsvBizId(isvBizId);
                if(alipayUser == null) {
                    log.info("saveAlipayUser not newest notify bizContent={}", bizContent);
                    return true;
                }

                alipayUser.setAuthToken(detail.getAppAuthToken());
                alipayUser.setRefreshToken(detail.getAppRefreshToken());
                alipayUser.setExpiresIn(authTime.plusSeconds(Long.parseLong(detail.getExpiresIn())));
                alipayUser.setReExpiresIn(authTime.plusSeconds(Long.parseLong(detail.getReExpiresIn())));
                alipayUser.setAlipayUserId(detail.getUserId());
                alipayUser.setStatus(AlipayUserStatus.YES);
                alipayUser.setIsDeleted(0);
                alipayUserManager.updateByPrimaryKeyWithVersion(alipayUser);
                success = true;
                break;
            } catch (Exception e) {
                log.info("saveAlipayUser error retry bizContent={}", bizContent, e);
            }
        }

        log.info("saveAlipayUser success bizContent={}", bizContent);

        return success;
    }
}
